<?php

declare(strict_types=1);

namespace Zlf\AppValidate\Rule;

use Zlf\AppException\Exception\ValidateException;
use Zlf\Unit\Is;

/**
 * 列表验证器
 *
 * 该类用于检查给定值是否符合指定的列表要求。它继承自 RuleAbstract，并提供了设置类型、长度限制、枚举数据、错误提示信息以及执行具体验证逻辑的方法。
 */
class ListValidate extends RuleAbstract
{
    /**
     * 默认错误消息
     *
     * 当验证失败时显示的默认错误消息。可以包含占位符 `{label}`，将在实际使用时替换为字段标签。
     *
     * @var string
     */
    protected $_error = '{label}未通过验证';

    /**
     * 列表内部类型
     *
     * 设置列表项应遵循的数据类型。默认是 'default'，表示不做特定类型的检查。
     *
     * @var string
     */
    protected $_type = 'default';

    /**
     * 类型错误提示信息
     *
     * 当列表项不符合指定类型时显示的错误消息。可以包含占位符 `{label}`。
     *
     * @var string
     */
    protected $_typeError = '{label}不符合要求';

    /**
     * 列表预期长度
     *
     * 如果设置，则列表长度必须严格等于此值。
     *
     * @var int
     */
    protected $_count = 0;

    /**
     * 长度错误提示信息
     *
     * 当列表长度不符合预期时显示的错误消息。可以包含占位符 `{label}` 和 `{count}`。
     *
     * @var string
     */
    protected $_countError = '{label}数量只能是{count}个';

    /**
     * 列表最小长度
     *
     * 如果设置，则列表长度不能少于此值。
     *
     * @var int
     */
    protected $_min = 0;

    /**
     * 最小长度错误提示信息
     *
     * 当列表长度小于最小值时显示的错误消息。可以包含占位符 `{label}` 和 `{min}`。
     *
     * @var string
     */
    protected $_minError = '{label}不能少于{min}个';

    /**
     * 列表最大长度
     *
     * 如果设置，则列表长度不能超过此值。
     *
     * @var int
     */
    protected $_max = 0;

    /**
     * 最大长度错误提示信息
     *
     * 当列表长度大于最大值时显示的错误消息。可以包含占位符 `{label}` 和 `{max}`。
     *
     * @var string
     */
    protected $_maxError = '{label}不能超过{max}个';

    /**
     * 枚举数据源
     *
     * 这些值被视为有效的选项。如果输入列表中的元素不在枚举数组中，则认为验证失败。
     *
     * @var array
     */
    protected $_enums = [];

    /**
     * 枚举错误提示信息
     *
     * 当列表包含无效的枚举值时显示的错误消息。可以包含占位符 `{label}`。
     *
     * @var string
     */
    protected $_enumsError = '{label}包含的选择项未通过验证';

    /**
     * 验证列表内部类型
     *
     * 允许调用者动态地设置或修改列表项的数据类型。
     *
     * @param string $type 新的列表项数据类型
     * @return ListValidate 返回当前对象实例，便于链式调用
     */
    public function setType(string $type): ListValidate
    {
        $this->_type = $type;
        return $this;
    }

    /**
     * 限制列表数据长度
     *
     * 允许调用者动态地设置或修改列表的预期长度。
     *
     * @param int $count 新的列表长度
     * @return ListValidate 返回当前对象实例，便于链式调用
     */
    public function setCount(int $count): ListValidate
    {
        $this->_count = $count;
        return $this;
    }

    /**
     * 限制列表数据长度错误信息
     *
     * 允许调用者动态地设置或修改列表长度错误提示信息。
     *
     * @param string $error 新的列表长度错误提示信息
     * @return ListValidate 返回当前对象实例，便于链式调用
     */
    public function setCountError(string $error): ListValidate
    {
        $this->_countError = $error;
        return $this;
    }

    /**
     * 限制列表数据最大长度
     *
     * 允许调用者动态地设置或修改列表的最大长度。
     *
     * @param int $max 新的最大长度
     * @return ListValidate 返回当前对象实例，便于链式调用
     */
    public function setMax(int $max): ListValidate
    {
        $this->_max = $max;
        return $this;
    }

    /**
     * 限制列表数据最大长度错误信息
     *
     * 允许调用者动态地设置或修改列表最大长度错误提示信息。
     *
     * @param string $error 新的最大长度错误提示信息
     * @return ListValidate 返回当前对象实例，便于链式调用
     */
    public function setMaxError(string $error): ListValidate
    {
        $this->_maxError = $error;
        return $this;
    }

    /**
     * 限制列表数据最小长度
     *
     * 允许调用者动态地设置或修改列表的最小长度。
     *
     * @param int $min 新的最小长度
     * @return ListValidate 返回当前对象实例，便于链式调用
     */
    public function setMin(int $min): ListValidate
    {
        $this->_min = $min;
        return $this;
    }

    /**
     * 限制列表最小数据长度错误信息
     *
     * 允许调用者动态地设置或修改列表最小长度错误提示信息。
     *
     * @param string $error 新的最小长度错误提示信息
     * @return ListValidate 返回当前对象实例，便于链式调用
     */
    public function setMinError(string $error): ListValidate
    {
        $this->_minError = $error;
        return $this;
    }

    /**
     * 设置枚举数据
     *
     * 允许调用者动态地设置或修改枚举值列表。支持传递数组或返回数组的可调用对象。
     *
     * @param array|callable $enums 新的枚举值列表或返回枚举值列表的可调用对象
     * @return ListValidate 返回当前对象实例，便于链式调用
     * @throws ValidateException 如果提供的枚举数据不是有效的列表，则抛出异常
     */
    public function setEnums($enums): ListValidate
    {
        if (is_callable($enums)) {
            $enum_list = $enums();
        } else {
            $enum_list = $enums;
        }
        if (Is::list($enum_list)) {
            $this->_enums = $enum_list;
            return $this;
        }
        throw new ValidateException('验证参数enums错误');
    }

    /**
     * 设置枚举错误提示信息
     *
     * 允许调用者动态地设置或修改枚举错误提示信息。
     *
     * @param string $error 新的枚举错误提示信息
     * @return ListValidate 返回当前对象实例，便于链式调用
     */
    public function setEnumsError(string $error): ListValidate
    {
        $this->_enumsError = $error;
        return $this;
    }

    /**
     * 开始验证
     *
     * 验证输入值是否为有效的列表，并检查是否满足类型、长度和枚举值的要求。
     * 如果输入值不是有效的列表或者不满足条件，则添加相应的错误信息并返回 false。
     *
     * @param mixed $value 待验证的值
     * @param string $label 字段标签，用于错误提示信息中
     * @return bool 验证结果：成功为 true，失败为 false
     */
    public function validate($value, string $label = ''): bool
    {
        if ((is_array($value) && Is::list($value))) {
            if (empty($value) && $this->_noSkipping === true) {
                $this->_value = $value;
                return true;
            }
            $valueCount = count($value);
            if ($this->_count > 0) {
                if ($valueCount != $this->_count) {
                    $this->addError(str_replace("{count}", strval($this->_count), $this->_countError), $label);
                }
            }
            if ($this->_min > 0) {
                if ($valueCount < $this->_min) {
                    $this->addError(str_replace("{min}", strval($this->_min), $this->_minError), $label);
                }
            }
            if ($this->_max > 0) {
                if ($valueCount > $this->_max) {
                    $this->addError(str_replace("{max}", strval($this->_max), $this->_maxError), $label);
                }
            }
            if ($this->_type !== 'default') {
                $this->checkTypeList($value, $label);
            }

            if (count($this->_enums) > 0) {
                $intersect = array_intersect($this->_enums, $value);
                if (count($intersect) < count($value)) {
                    $this->addError($this->_enumsError, $label);
                }
            }

            if ($this->isFail()) {
                return false;
            }

            $this->_value = $value;
            return true;
        }
        return $this->addError($this->_error, $label);
    }

    /**
     * 检查列表项的数据类型
     *
     * 根据配置的数据类型检查列表中的每个元素是否符合要求。
     *
     * @param array $value 要检查的列表
     * @param string $label 字段标签，用于错误提示信息中
     */
    private function checkTypeList(array $value, string $label)
    {
        if ($this->_type === 'number') {
            foreach ($value as $val) {
                if (!is_numeric($val)) {
                    $this->addError($this->_typeError, $label);
                    break;
                }
            }
        } elseif ($this->_type === 'string') {
            foreach ($value as $val) {
                if (!is_string($val)) {
                    $this->addError($this->_typeError, $label);
                    break;
                }
            }
        }
    }
}